package ch.fusun.baron.basic.command;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

import ch.fusun.baron.city.City;
import ch.fusun.baron.city.api.CityService;
import ch.fusun.baron.core.injection.Configure;
import ch.fusun.baron.core.injection.Inject;
import ch.fusun.baron.core.service.UserService;
import ch.fusun.baron.map.Tile;
import ch.fusun.baron.map.api.GameMapService;
import ch.fusun.baron.player.Dynasty;
import ch.fusun.baron.printing.MessagingService;
import ch.fusun.baron.property.api.PropertyService;
import ch.fusun.baron.unit.Unit;
import ch.fusun.baron.unit.service.UnitService;

/**
 * Moves a unit from one tile to the next
 */
public class MoveUnitCommand extends MoneyTurnCommand {

	private Dynasty dynasty;
	private Unit unit;
	private Tile destination;
	@Inject
	private transient PropertyService propertyService;
	@Inject
	private transient UnitService unitService;
	@Inject
	private transient CityService cityService;
	@Inject
	private transient GameMapService mapService;
	@Inject
	private transient MessagingService messagingService;
	@Inject
	private transient UserService userService;
	@Configure(value = "10")
	private transient int MONEY_COST;

	/**
	 * Injection constructor
	 */
	public MoveUnitCommand() {
	}

	/**
	 * @param dynasty
	 *            The player
	 * @param unit
	 *            the unit
	 * @param destination
	 *            THe destination tile
	 */
	public MoveUnitCommand(Dynasty dynasty, Unit unit, Tile destination) {
		this.dynasty = dynasty;
		this.unit = unit;
		this.destination = destination;
	}

	@Override
	public boolean isAllowedImpl() {
		Collection<Unit> units = unitService.getUnits(destination);
		boolean destinationOccupied = false;
		if (!units.isEmpty()) {
			Unit otherUnit = units.iterator().next();
			destinationOccupied = dynasty.equals(propertyService
					.getOwnership(otherUnit));
		}
		return dynasty.equals(propertyService.getOwnership(unit))
				&& tileIsReachable() && unitService.unitExists(unit)
				&& !destinationOccupied;
	}

	private boolean tileIsReachable() {
		return !(unitService.getLocation(unit).absoluteDistanceTo(destination) > 1f);
	}

	@Override
	public void executeImpl() {
		Unit movingUnit = unitService.getUnits(unitService.getLocation(unit))
				.iterator().next();
		Collection<Unit> units = unitService.getUnits(destination);
		if (units.isEmpty()) {
			unitService.moveUnit(movingUnit, destination);
			if (calculateAnnectionFrom()) {
				messagingService
						.addMessage(
								userService.getUser(),
								"City of " //$NON-NLS-1$
										+ ((Dynasty) propertyService
												.getOwnership(unit)).getName()
										+ " is lost because no one defended it."); //$NON-NLS-1$
			}
		} else {
			Unit otherUnit = units.iterator().next();
			if (!dynasty.equals(propertyService.getOwnership(otherUnit))) {
				int numberOfUnits = movingUnit.getNumberOfUnits();
				int otherNumberOfUnits = otherUnit.getNumberOfUnits();
				int casualties = 0;
				int otherCasualties = 0;
				for (int i = 1; i <= Math
						.max(numberOfUnits, otherNumberOfUnits); i++) {
					if (i > numberOfUnits) {
						casualties += (Math.random() < 0.5f) ? 1 : 0;
					} else if (i > otherNumberOfUnits) {
						otherCasualties += (Math.random() < 0.5f) ? 1 : 0;
					} else if (Math.random() < 0.5f) {
						casualties += 1;
					} else {
						otherCasualties += 1;
					}
				}
				messagingService
						.addMessage(
								userService.getUser(),
								"Battle between " //$NON-NLS-1$
										+ ((Dynasty) propertyService
												.getOwnership(unit)).getName()
										+ "(" + casualties + " casualties) and " //$NON-NLS-1$ //$NON-NLS-2$
										+ ((Dynasty) propertyService
												.getOwnership(otherUnit))
												.getName() + "(" //$NON-NLS-1$
										+ otherCasualties + " casualties)"); //$NON-NLS-1$
				movingUnit.kill(casualties);
				if (movingUnit.isDead()) {
					unitService.removeUnit(movingUnit);
					messagingService.addMessage(
							userService.getUser(),
							"Attacking unit of " //$NON-NLS-1$
									+ ((Dynasty) propertyService
											.getOwnership(unit)).getName()
									+ " exploded in a fireball"); //$NON-NLS-1$
				}
				otherUnit.kill(otherCasualties);
				if (otherUnit.isDead()) {
					messagingService.addMessage(
							userService.getUser(),
							"Defending unit of " //$NON-NLS-1$
									+ ((Dynasty) propertyService
											.getOwnership(unit)).getName()
									+ " was trampled"); //$NON-NLS-1$
					unitService.removeUnit(otherUnit);
					unitService.moveUnit(movingUnit, destination);
					if (calculateAnnectionFrom()) {
						messagingService
								.addMessage(
										userService.getUser(),
										"City of " //$NON-NLS-1$
												+ ((Dynasty) propertyService
														.getOwnership(unit))
														.getName()
												+ " is lost because defenders were to weak."); //$NON-NLS-1$
					}
				}
			}
		}
	}

	private boolean calculateAnnectionFrom() {
		boolean newLand = false;
		Object o = propertyService.getOwnership(destination);
		if (o instanceof Dynasty) {
			Dynasty ownership = (Dynasty) o;
			if (!dynasty.equals(ownership)) {
				City city = cityService.getCity(destination);
				if (city != null) {
					Collection<City> citiesOfPlayer = getCitiesForDynasty(ownership);
					for (Tile tile : mapService.getTiles()) {
						if (ownership
								.equals(propertyService.getOwnership(tile))) {
							if (getNearestCityLocation(tile, citiesOfPlayer)
									.equals(destination)) {
								propertyService.setOwnership(dynasty, tile);
								newLand = true;
							}
						}
					}
				}
			}
		}
		return newLand;
	}

	private Tile getNearestCityLocation(Tile tile, Collection<City> cities) {
		Tile nearestLocation = new Tile(Integer.MAX_VALUE, Integer.MAX_VALUE);
		double nearestDistance = Double.MAX_VALUE;
		for (City city : cities) {
			Tile cityLocation = cityService.getLocation(city);
			if (cityLocation.absoluteDistanceTo(tile) < nearestDistance) {
				nearestDistance = cityLocation.absoluteDistanceTo(tile);
				nearestLocation = cityLocation;
			}
		}
		return nearestLocation;
	}

	private Collection<City> getCitiesForDynasty(Dynasty dynasty) {
		List<City> cities = new LinkedList<City>();
		for (Tile tile : mapService.getTiles()) {
			City city = cityService.getCity(tile);
			if (city != null) {
				if (dynasty.equals(propertyService.getOwnership(tile))) {
					cities.add(city);
				}
			}
		}
		return cities;
	}

	@Override
	protected int getTurnCost() {
		return unit.getNumberOfUnits();
	}

	@Override
	protected Dynasty getDynasty() {
		return dynasty;
	}

	@Override
	protected int getMoneyCost() {
		return MONEY_COST;
	}

}
